// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UIConfigurationEditor.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.UI.Forms
{
    #region Using Directives

    using System;
    using System.ComponentModel;
    using System.Drawing.Design;
    using System.Globalization;
    using System.IO;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;

    using Org.OpenTrader.Framework.Entities;
    using Org.OpenTrader.Framework.Enums;
    using Org.OpenTrader.Framework.LiveObjects;
    using Org.OpenTrader.Framework.LiveObjects.Interfaces;

    #endregion

    /// <summary>
    /// The ui configuration editor.
    /// </summary>
    public partial class UIConfigurationEditor : UIControl
    {
        #region Constructors and Destructors

        /// <summary>
        /// Initializes static members of the <see cref="UIConfigurationEditor"/> class.
        /// </summary>
        static UIConfigurationEditor()
        {
            TypeDescriptor.AddProvider(new ConfigurationType.TypeDescriptionProvider(), typeof(IConfiguration));

            TypeDescriptor.AddProvider(new ConfigurationType.TypeDescriptionProvider(), typeof(Configuration));
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="UIConfigurationEditor"/> class.
        /// </summary>
        public UIConfigurationEditor()
        {
            this.InitializeComponent();

            ToolStrip toolstrip = null;
            foreach (var c in this.propertyGrid.Controls)
            {
                if (c is ToolStrip)
                {
                    toolstrip = c as ToolStrip;
                    break;
                }
            }

            if (toolstrip == null)
            {
                return;
            }

            toolstrip.Items.Add(new ToolStripSeparator());
            var importItem = new ToolStripButton("Import");
            toolstrip.Items.Add(importItem);
            toolstrip.Items.Add(new ToolStripSeparator());
            var exportItem = new ToolStripButton("Export");
            toolstrip.Items.Add(exportItem);

            importItem.Click += this.OnImportItemClick;
            exportItem.Click += this.OnExportItemClick;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets Configuration.
        /// </summary>
        public IConfiguration Configuration
        {
            get
            {
                return this.propertyGrid.SelectedObject as IConfiguration;
            }

            set
            {
                this.propertyGrid.SelectedObject = value;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Edit the configuration of given object
        /// </summary>
        /// <param name="configurationId">
        /// The configurationId.
        /// </param>
        /// <param name="schema">
        /// The schema.
        /// </param>
        /// <param name="container">
        /// The container.
        /// </param>
        public static void EditConfiguration(Guid configurationId, string schema, ILiveObjectContainer container)
        {
            var configuration = DBConfigurationHelpers.LoadConfiguration(configurationId, schema, container);

            var ctrl = new UIConfigurationEditor();
            ctrl.Configuration = configuration;
            var form = ctrl.CreateContainingForm("Configuration");
            form.Visibility = EUIFormVisibility.ShowDialog;

            // Save to DB
            DBConfigurationHelpers.SaveConfiguration(configuration);
        }

        #endregion

        #region Methods

        /// <summary>
        /// The exportItem_ click.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        /// <exception cref="NotImplementedException">
        /// </exception>
        private void OnExportItemClick(object sender, EventArgs e)
        {
            this.folderBrowserDialog1.ShowNewFolderButton = true;
            this.folderBrowserDialog1.SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
            var result = this.folderBrowserDialog1.ShowDialog();
            if (result == DialogResult.OK)
            {
                var ms = new MemoryStream();
                var sw = new StreamWriter(ms);
                sw.AutoFlush = true;
                var context = new SerializationContext(this.folderBrowserDialog1.SelectedPath);
                var status = this.Configuration.Serialize(sw, context);
                if (!status)
                {
                    MessageBox.Show(status.DetailedMessage, "Export failed");
                }
                else
                {
                    SerializationContext.SaveToDisk("config.otcfg", ms.ToArray(), context);
                }

                sw.Close();
            }
        }

        /// <summary>
        /// The importItem_ click.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        /// <exception cref="NotImplementedException">
        /// </exception>
        private void OnImportItemClick(object sender, EventArgs e)
        {
            this.folderBrowserDialog1.ShowNewFolderButton = false;
            this.folderBrowserDialog1.SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
            var result = this.folderBrowserDialog1.ShowDialog();
            if (result == DialogResult.OK)
            {
                var path = this.folderBrowserDialog1.SelectedPath + @"\config.otcfg";

                if (! File.Exists(path))
                {
                    MessageBox.Show(string.Format("No config.otcfg was found in directory {0}" + path), "Import failed");
                }

                try
                {
                    var context = new SerializationContext(this.folderBrowserDialog1.SelectedPath);
                    var bytes = File.ReadAllBytes(path);
                    var ms = new MemoryStream(bytes, false);
                    ms.Seek(0, SeekOrigin.Begin);
                    var sr = new StreamReader(ms);
                    var status = this.Configuration.DeSerialize(sr, context);
                    if (!status)
                    {
                        MessageBox.Show(status.DetailedMessage, "Import failed");
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Got exception" + ex, "Import failed");
                }
            }

            this.propertyGrid.Refresh();
        }

        #endregion

        /// <summary>
        /// The configuration type.
        /// </summary>
        internal class ConfigurationType
        {
            /// <summary>
            /// The type description provider.
            /// </summary>
            public class TypeDescriptionProvider : System.ComponentModel.TypeDescriptionProvider
            {
                #region Constants and Fields

                /// <summary>
                /// The configuration.
                /// </summary>
                private IConfiguration configuration;

                /// <summary>
                /// The type.
                /// </summary>
                private Type type;

                #endregion

                #region Public Methods

                /// <summary>
                /// The get type descriptor.
                /// </summary>
                /// <param name="objectType">
                /// The object type.
                /// </param>
                /// <param name="instance">
                /// The instance.
                /// </param>
                /// <returns>
                /// </returns>
                public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
                {
                    this.configuration = instance as IConfiguration;
                    this.type = objectType;

                    return new TypeDescriptor(this.configuration, this.type);
                }

                #endregion

                /// <summary>
                /// The converter.
                /// </summary>
                internal class Converter : StringConverter
                {
                    #region Public Methods

                    /// <summary>
                    /// The convert to.
                    /// </summary>
                    /// <param name="context">
                    /// The context.
                    /// </param>
                    /// <param name="culture">
                    /// The culture.
                    /// </param>
                    /// <param name="value">
                    /// The value.
                    /// </param>
                    /// <param name="destinationType">
                    /// The destination type.
                    /// </param>
                    /// <returns>
                    /// The convert to.
                    /// </returns>
                    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
                    {
                        var o = value as IConfiguration;
                        if (o != null)
                        {
                            if (o.ConfigurationSchema.Fields.Length > 0)
                            {
                                return string.Format("Edit -->");
                            }
                            else
                            {
                                return string.Format("No fields");
                            }
                        }

                        if (value == null)
                        {
                            return "null configuration";
                        }

                        return "not an IConfiguration";
                    }

                    #endregion
                }

                /// <summary>
                /// The editor.
                /// </summary>
                internal class Editor : UITypeEditor
                {
                    // Indicates whether the UITypeEditor provides a form-based (modal) dialog, 
                    // drop down dialog, or no UI outside of the properties window.

                    // Displays the UI for value selection.
                    #region Public Methods

                    /// <summary>
                    /// The edit value.
                    /// </summary>
                    /// <param name="context">
                    /// The context.
                    /// </param>
                    /// <param name="provider">
                    /// The provider.
                    /// </param>
                    /// <param name="value">
                    /// The value.
                    /// </param>
                    /// <returns>
                    /// The edit value.
                    /// </returns>
                    public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
                    {
                        // Uses the IWindowsFormsEditorService to display a 
                        // drop-down UI in the Properties window.
                        var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
                        if (editorService != null)
                        {
                            // ObjectEditorControl f = new ObjectEditorControl();
                            // f.Instance = value as IObject;
                            // edSvc.DropDownControl(f);
                            return value;
                        }

                        return value;
                    }

                    /// <summary>
                    /// The get edit style.
                    /// </summary>
                    /// <param name="context">
                    /// The context.
                    /// </param>
                    /// <returns>
                    /// </returns>
                    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
                    {
                        return UITypeEditorEditStyle.DropDown;
                    }

                    #endregion
                }

                /// <summary>
                /// The property.
                /// </summary>
                internal class Property
                {
                    #region Constants and Fields

                    /// <summary>
                    /// The category.
                    /// </summary>
                    private string category = string.Empty;

                    /// <summary>
                    /// The desc.
                    /// </summary>
                    private string desc = string.Empty;

                    /// <summary>
                    /// The readonly.
                    /// </summary>
                    private bool isReadonly = false;

                    /// <summary>
                    /// The name.
                    /// </summary>
                    private string name = string.Empty;

                    /// <summary>
                    /// The type.
                    /// </summary>
                    private Type type;

                    /// <summary>
                    /// The visible.
                    /// </summary>
                    private bool visible = true;

                    #endregion

                    #region Constructors and Destructors

                    /// <summary>
                    /// Initializes a new instance of the <see cref="Property"/> class.
                    /// </summary>
                    /// <param name="name">
                    /// The name.
                    /// </param>
                    /// <param name="type">
                    /// The type.
                    /// </param>
                    public Property(string name, Type type)
                    {
                        this.name = name;
                        this.type = type;
                    }

                    #endregion

                    #region Properties

                    /// <summary>
                    /// Gets or sets Category.
                    /// </summary>
                    public string Category
                    {
                        get
                        {
                            return this.category;
                        }

                        set
                        {
                            this.category = value;
                        }
                    }

                    /// <summary>
                    /// Gets or sets Description.
                    /// </summary>
                    public string Description
                    {
                        get
                        {
                            return this.desc;
                        }

                        set
                        {
                            this.desc = value;
                        }
                    }

                    /// <summary>
                    /// Gets Name.
                    /// </summary>
                    public string Name
                    {
                        get
                        {
                            return this.name;
                        }
                    }

                    /// <summary>
                    /// Gets or sets a value indicating whether ReadOnly.
                    /// </summary>
                    public bool ReadOnly
                    {
                        get
                        {
                            return this.isReadonly;
                        }

                        set
                        {
                            this.isReadonly = value;
                        }
                    }

                    /// <summary>
                    /// Gets Type.
                    /// </summary>
                    public Type Type
                    {
                        get
                        {
                            return this.type;
                        }
                    }

                    /// <summary>
                    /// Gets or sets a value indicating whether Visible.
                    /// </summary>
                    public bool Visible
                    {
                        get
                        {
                            return this.visible;
                        }

                        set
                        {
                            this.visible = value;
                        }
                    }

                    #endregion
                }

                /// <summary>
                /// The property descriptor.
                /// </summary>
                internal class PropertyDescriptor : System.ComponentModel.PropertyDescriptor
                {
                    #region Constants and Fields

                    /// <summary>
                    /// The configuration.
                    /// </summary>
                    private readonly IConfiguration configuration;

                    /// <summary>
                    /// The _ property.
                    /// </summary>
                    private readonly Property property;

                    #endregion

                    #region Constructors and Destructors

                    /// <summary>
                    /// Initializes a new instance of the <see cref="PropertyDescriptor"/> class.
                    /// </summary>
                    /// <param name="config">
                    /// The config.
                    /// </param>
                    /// <param name="myProperty">
                    /// The my property.
                    /// </param>
                    /// <param name="attrs">
                    /// The attrs.
                    /// </param>
                    public PropertyDescriptor(IConfiguration config, ref Property myProperty, Attribute[] attrs)
                        : base(myProperty.Name, attrs)
                    {
                        this.property = myProperty;
                        this.configuration = config;
                    }

                    #endregion

                    #region Properties

                    /// <summary>
                    /// Gets Category.
                    /// </summary>
                    public override string Category
                    {
                        get
                        {
                            return this.property.Category;
                        }
                    }

                    /// <summary>
                    /// Gets ComponentType.
                    /// </summary>
                    public override Type ComponentType
                    {
                        get
                        {
                            return null;
                        }
                    }

                    /// <summary>
                    /// Gets Description.
                    /// </summary>
                    public override string Description
                    {
                        get
                        {
                            return this.property.Description;
                        }
                    }

                    /// <summary>
                    /// Gets DisplayName.
                    /// </summary>
                    public override string DisplayName
                    {
                        get
                        {
                            return this.property.Name;
                        }
                    }

                    /// <summary>
                    /// Gets a value indicating whether IsReadOnly.
                    /// </summary>
                    public override bool IsReadOnly
                    {
                        get
                        {
                            return this.property.ReadOnly;
                        }
                    }

                    /// <summary>
                    /// Gets PropertyType.
                    /// </summary>
                    public override Type PropertyType
                    {
                        get
                        {
                            return this.property.Type;
                        }
                    }

                    #endregion

                    #region Public Methods

                    /// <summary>
                    /// The can reset value.
                    /// </summary>
                    /// <param name="component">
                    /// The component.
                    /// </param>
                    /// <returns>
                    /// The can reset value.
                    /// </returns>
                    public override bool CanResetValue(object component)
                    {
                        return false;
                    }

                    /// <summary>
                    /// The get value.
                    /// </summary>
                    /// <param name="component">
                    /// The component.
                    /// </param>
                    /// <returns>
                    /// The get value.
                    /// </returns>
                    public override object GetValue(object component)
                    {
                        return this.configuration[this.property.Name];
                    }

                    /// <summary>
                    /// The reset value.
                    /// </summary>
                    /// <param name="component">
                    /// The component.
                    /// </param>
                    public override void ResetValue(object component)
                    {
                    }

                    /// <summary>
                    /// The set value.
                    /// </summary>
                    /// <param name="component">
                    /// The component.
                    /// </param>
                    /// <param name="value">
                    /// The value.
                    /// </param>
                    public override void SetValue(object component, object value)
                    {
                        var mm = new MemoryStream(System.Text.ASCIIEncoding.ASCII.GetBytes(value.ToString()));
                        var sr = new StreamReader(mm);
                        var status = this.configuration[this.property.Name].DeSerialize(sr, new SerializationContext());

                        // Raise event
                        this.OnValueChanged(component, EventArgs.Empty);
                    }

                    /// <summary>
                    /// The should serialize value.
                    /// </summary>
                    /// <param name="component">
                    /// The component.
                    /// </param>
                    /// <returns>
                    /// The should serialize value.
                    /// </returns>
                    public override bool ShouldSerializeValue(object component)
                    {
                        return false;
                    }

                    #endregion
                }

                /// <summary>
                /// The type descriptor.
                /// </summary>
                internal class TypeDescriptor : CustomTypeDescriptor
                {
                    #region Constants and Fields

                    /// <summary>
                    /// The configuration.
                    /// </summary>
                    private IConfiguration configuration;

                    /// <summary>
                    /// The type.
                    /// </summary>
                    private Type type;

                    #endregion

                    #region Constructors and Destructors

                    /// <summary>
                    /// Initializes a new instance of the <see cref="TypeDescriptor"/> class.
                    /// </summary>
                    /// <param name="config">
                    /// The config.
                    /// </param>
                    /// <param name="type">
                    /// The type.
                    /// </param>
                    public TypeDescriptor(IConfiguration config, Type type)
                    {
                        this.configuration = config;
                        this.type = type;

                        if (config == null)
                        {
                        }
                    }

                    #endregion

                    #region Public Methods

                    /// <summary>
                    /// The get converter.
                    /// </summary>
                    /// <returns>
                    /// </returns>
                    public override TypeConverter GetConverter()
                    {
                        return new Converter();
                    }

                    /// <summary>
                    /// The get editor.
                    /// </summary>
                    /// <param name="editorBaseType">
                    /// The editor base type.
                    /// </param>
                    /// <returns>
                    /// The get editor.
                    /// </returns>
                    public override object GetEditor(Type editorBaseType)
                    {
                        return new Editor();
                    }

                    /// <summary>
                    /// The get properties.
                    /// </summary>
                    /// <returns>
                    /// </returns>
                    public override PropertyDescriptorCollection GetProperties()
                    {
                        return this.GetProperties(null);
                    }

                    /// <summary>
                    /// The get properties.
                    /// </summary>
                    /// <param name="attributes">
                    /// The attributes.
                    /// </param>
                    /// <returns>
                    /// </returns>
                    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
                    {
                        var props = new PropertyDescriptorCollection(null);
                        if (this.configuration == null)
                        {
                            return props;
                        }

                        foreach (var field in this.configuration.ConfigurationSchema.Fields)
                        {
                            var currentField = this.configuration.ConfigurationSchema.GetFieldDescriptor(field);
                            var tip = currentField.ToolTip == null ? "ToolTip not Available" : currentField.ToolTip;

                            var type = currentField.Type;

                            var prop = new Property(field, type);
                            if (currentField.IsMandatory)
                            {
                                prop.Category = "Mandatory";
                            }
                            else
                            {
                                prop.Category = "Optional";
                            }

                            prop.Description = string.Format("Type: {0}\nTip: {1}", type, tip);

                            props.Add(new PropertyDescriptor(this.configuration, ref prop, attributes));
                        }

                        return props;
                    }

                    #endregion
                }
            }
        }
    }
}